package naga.x.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import gnu.trove.map.TShortObjectMap;
import gnu.trove.map.hash.TShortObjectHashMap;
import gnu.trove.procedure.TShortObjectProcedure;
import gnu.trove.procedure.TShortProcedure;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtNewConstructor;
import javassist.CtNewMethod;
import naga.x.App;
import naga.x.net.Packet;
import naga.x.net.PacketHandler;
import naga.x.net.Session;
import naga.x.net.annotation.OP;
import naga.x.net.annotation.OPHandler;
import naga.x.net.impl.pomelo.PomeloPacket;
import naga.x.net.impl.pomelo.annotation.PomeloOP;
import naga.x.service.Service;

public class ServiceUtils {
	
	private ServiceUtils() {}
	
	public static <T extends Service> T newService(Class<T> serviceClass) throws Exception {
		return newService(serviceClass, null);
	}

	public static <T extends Service> T newService(Class<T> serviceClass, Class<?>[] argTypes, Object... args) throws Exception {
		return newPacketHandlerProxy(serviceClass, argTypes, args);
	}
	
	public static <T> T newPacketHandlerProxy(Class<T> javaclass) throws Exception {
		return newPacketHandlerProxy(javaclass, null);
	}
	
	public static <T> T newPacketHandlerProxy(Class<T> javaclass, Class<?>[] argTypes, Object... args) throws Exception {
		if (javaclass.getDeclaredAnnotation(OPHandler.class) != null) {
			Method[] methods = javaclass.getDeclaredMethods();
			TShortObjectMap<Method> opcodes = null;
			for (Method m : methods) {
				OP op = m.getDeclaredAnnotation(OP.class);
				if (op != null) {
					Class<?>[] params = m.getParameterTypes();
					if (params.length != 2 || params[0] != Session.class || !Packet.class.isAssignableFrom(params[1])) {
						throw new IllegalArgumentException(javaclass.getName() + "." + m.getName() + " parameter error!");
					}
					if (opcodes == null) {
						opcodes = new TShortObjectHashMap<Method>();
					}
					opcodes.put(op.value(), m);
				}
				PomeloOP pop = m.getDeclaredAnnotation(PomeloOP.class);
				if (pop != null) {
					Class<?>[] params = m.getParameterTypes();
					if (params.length != 2 || params[0] != Session.class || !PomeloPacket.class.isAssignableFrom(params[1])) {
						throw new IllegalArgumentException(javaclass.getName() + "." + m.getName() + " parameter error!");
					}
					short opcode = pop.code() != -1 ? pop.code() : App.getApp().getConfig().getDictionary().getRouteCode(pop.value());
					if (opcode != -1) {
						if (opcodes == null) {
							opcodes = new TShortObjectHashMap<Method>();
						}
						opcodes.put(opcode, m);
					}
				}
			}
			
			if (opcodes != null) {
				ClassPool pool = ClassPool.getDefault();
				String proxyClassName = javaclass.getName() + "_Proxy";
				try {
					javaclass = (Class<T>) pool.getClassLoader().loadClass(proxyClassName);
				} catch (ClassNotFoundException e) {
					CtClass declaring = pool.makeClass(proxyClassName, pool.get(javaclass.getName()));
					declaring.addInterface(pool.get(PacketHandler.class.getName()));
					StringBuilder code = new StringBuilder(64);
					// 构造方法
					if (argTypes != null && argTypes.length > 0) {
						code.append("public ").append(declaring.getSimpleName()).append("(");
						for (int i = 0; i < argTypes.length; i++) {
							Class<?> type = argTypes[i];
							if (i != 0) {
								code.append(", ");
							}
							code.append(type.getName()).append(" arg").append(i);
						}
						code.append(") { super(");
						for (int i = 1; i <= argTypes.length; i++) {
							if (i != 1) {
								code.append(", ");
							}
							code.append("$").append(i);
						}
						code.append("); }");
						declaring.addConstructor(CtNewConstructor.make(code.toString(), declaring));
						code.setLength(0); // clear the buffer
					}
					// 包处理方法
					code.append("public void handle(naga.x.net.Session session, naga.x.net.Packet packet) {");
					code.append("switch ($2.getOpCode()) {");
					opcodes.forEachEntry(new TShortObjectProcedure<Method>() {
						@Override
						public boolean execute(short op, Method method) {
							code.append("case ").append(op).append(':');
							String dataType = method.getParameterTypes()[1].getName();
							code.append(method.getName()).append("($1, (").append(dataType).append(") $2);");
							code.append("break;");
							return true;
						}
					});
					code.append("}}");
					declaring.addMethod(CtNewMethod.make(code.toString(), declaring));
					javaclass = declaring.toClass();
				}
				
				Constructor<T> con = javaclass.getConstructor(argTypes);
				final T proxy = con.newInstance(args);
				opcodes.forEachKey(new TShortProcedure() {
					@Override
					public boolean execute(short op) {
						App.getApp().getPacketDispatcher().register(op, (PacketHandler) proxy);
						return true;
					}
				});
				
//				File f = new File("service_proxy", declaring.getSimpleName() + ".class");
//				IOUtils.write(f, declaring.toBytecode());
//				System.out.println("packet handler-service:" + f.getAbsolutePath());
				
				return proxy;
			}
		}
		Constructor<T> con = javaclass.getConstructor(argTypes);
		return con.newInstance(args);
	}
	
}
